Skip to content

fix: hotbar tools, shulker boxes and drops - #353

Open
blocksimus wants to merge 13 commits into
lambda-client:1.21.11from
blocksimus:stack-fixes
Open

blocksimus wants to merge 13 commits into
lambda-client:1.21.11from
blocksimus:stack-fixes

Conversation

@blocksimus

Copy link
Copy Markdown
Contributor

Description

A few things I ran into after the rewrite... feel free to pick them apart.

Hotbar swapping behavior - hotbar gave up the smallest stack first so tools were getting moved out. The default sort by count meant any sort added after it only broke ties. Not as happy with the code here, so if you have any better thoughts...

A shulker box in the hotbar never opened (it tried to move it from the hotbar to the hotbar), nothing could be taken out of an open one so builds kept placing and breaking it, and a box couldn't go back while an identical one was in the hotbar. Opening uses a withContents filter now instead of sortedByBestContentMatch, because acquireStack grabs any matching box already in the hotbar, so a same-looking box with different contents would get opened instead.

Collecting drops only set Baritone's goal when the drop moved, so if another task stopped Baritone on the way it just sat there. Now it sets it again after Baritone's been idle for a second.

Selections kept their default sort by count when mutated, so any sort
added after it only broke ties. A full hotbar then gave up its smallest
stack first, which was usually a lone sword or tool.
A box already in the hotbar never opened, nothing could be taken out
of an open box so builds kept placing and breaking it, and a box
couldn't go back while an identical one was in the hotbar.
Collecting drops only set Baritone's goal when the drop moved, so if
another task stopped Baritone on the way it just waited forever. Now
it sets it again after Baritone has been idle for a second.
@github-project-automation github-project-automation Bot moved this to Backlog in Kanban Oct 22, 2026
@beanbag44
beanbag44 self-requested a review October 22, 2026 09:56
.asSequence()
.filter { it.stack.item == shulkerItem && it.stack.name.string == itemName }
.firstNotNullOfOrNull { it.slot }
if (pickedUp != null && containedIn.slots.any { it.identity() == pickedUp }) return@thenOrNull null

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, maybe we could adjust the container transfer task to allow swapping within the same container, just checking that the slots arent the same? I dont see how that could break any functionality. Then to avoid a needless transfer we could check to see if the original slot the shulker came from is empty and the shulker is already in the correct container before doing the transfer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I shortcut this by not bothering if it was just going back into your inventory/hotbar (not external), unless you think it's important to try get it back where it was previously?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible at least. If the slot isnt empty anymore then dont bother, but i think for the sake of inventory cleanliness its best to try

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, the only skip is the box already being back in its container with its own slot filled.

Comment thread src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt Outdated
@blocksimus

Copy link
Copy Markdown
Contributor Author

Thanks for the review! I'll try get another iteration done soon.

Picks boxes by slot, hands back the placed box instead of
keeping it on the container, and only puts boxes back into
chests.
The Pre stage swapped a kept slot back before it was asked again,
so a swap pause never ran out.
private fun SafeContext.checkResetSwap() {
activeRequest?.let { active ->
if (active.keepTicks <= 0) {
if (active.keepTicks == 0 && active.activeRequestAge > 0 && tickStage != requestStage) return

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixes an issue I hit with my Nuker's Server Swap at 2. The slot got swapped back at Pre (now in the default mask) before the break asked again, so Nuker stalled.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah i wondered if changing the default would cause issues. Wouldnt we only need the tickStage != requestStage check though? keepTicks is checked to be <= 0 just before, and the active.activeRequestAge > 0 would never prevent the return for keepTicks 0 requests as they start and end on the same event

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, the activeRequestAge > 0 was unnecessary. I think == 0 is still helpful... if a request becomes overdue, it can swap back at any stage, not just its own.

@blocksimus
blocksimus requested a review from beanbag44 October 23, 2026 09:03

@beanbag44 beanbag44 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lemme know if this is too much, i can always help out; i think the pr's have maintainer write perms enabled by default

}.then { pos ->
openContainer(pos).onSuccess {
success(OpenedShulkerBoxContext(pos))
val placed = PlacedShulkerBoxContainer(pos, Block.getBlockFromItem(shulkerItem), null, stacks)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm containers should only really be created in ContainerHandler and ContainerSerializer. Seeing as openContainer accesses the placed shulker, you can probably receive it from ContainerHandler.accessedPlacedContainer. Or actually its likely best just to change the return type of openContainer to T : PlacedContainer with the class taking a type parameter

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See what you think...

private fun SafeContext.checkResetSwap() {
activeRequest?.let { active ->
if (active.keepTicks <= 0) {
if (active.keepTicks == 0 && active.activeRequestAge > 0 && tickStage != requestStage) return

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah i wondered if changing the default would cause issues. Wouldnt we only need the tickStage != requestStage check though? keepTicks is checked to be <= 0 just before, and the active.activeRequestAge > 0 would never prevent the return for keepTicks 0 requests as they start and end on the same event

Comment thread src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
Comment thread src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt Outdated
Comment thread src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt Outdated
.toStructure(TargetState.Stack(slot.stack))
.toBlueprint()
.build(finishOnDone = true, collectDrops = false)
.onPreTick { slot in HotbarContainer.slots && !this.hotbarRequest(slot.index).submit().done }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm something about this feels off lol but i think the whole TargetState style system needs improving, so that can wait for a later date

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it does, and I was getting into a mess working through some edge cases... have a look at the change in InteractSim instead.

@blocksimus

Copy link
Copy Markdown
Contributor Author

Lemme know if this is too much, i can always help out; i think the pr's have maintainer write perms enabled by default

No stress, it's been a good exercise in getting to know the codebase, although feel free to make changes as you'd like.

@blocksimus
blocksimus requested a review from beanbag44 October 24, 2026 04:13
A closed box goes back into its own slot when it's free, or stays
where it was picked up if it can't go back.
…ner casting into the OpenContainerTask, add SlotIdentity builder functions in StackSelection and use slotsAvailable for the matching inventory check in OpenedShulkerBoxContext.

@beanbag44 beanbag44 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made some changes too

countEmptyAsOne()
alwaysWholeStacks()
item?.let { isItem(it) }
(targetState as? TargetState.Stack)?.let { target ->

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is better, I'll add a todo in TargetState to rethink those classes at a later date

TransferResult(
fromStack.mutate {
sortedWith {
sortedFirstWith {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather keep stack and container selections "immutable through mutation" in the sense that predicates and sorting from the initial creation can never be tampered with, only added to. This keeps it simple for a developer to know the exact base level restrictions and preferences a selection has

@beanbag44

Copy link
Copy Markdown
Member

I think a simple index comparison instead of the isPreferredOver call keeps things simple as theres also the chance the selections sorting discriminates based on stack size or stack contents which seems like a code smell to me. We are trying to find a slot that would better suit the stack, not judging the stack itself

@beanbag44

Copy link
Copy Markdown
Member

Feel free to revert/change things ofc

sides: Set<Direction> = Direction.entries.toSet()
) = OpenContainerTask<T>(blockPos, sides, automated)

class OpenContainerTask<R : PlacedContainer> @Ta5kBuilder @PublishedApi internal constructor(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't work for ender chests at the moment, unless we create a PlacedEnderChestContainer that would delegate to the EnderChestContainer singleton?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah i didnt catch that, i suppose we can just change it from PlacedContainer to Container

@blocksimus blocksimus Oct 26, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had R : Any so the untyped openContainer could still work and return the ScreenHandler, so things that aren't indexed (barrels, hoppers, furnaces etc.) can still be opened with it. With R : Container they'd fail to open... StashMover opens any LootableContainerBlockEntity that way. Otherwise I could make it R : Container? instead, and callers would grab the ScreenHandler themselves... null could mean either unindexed or wrong type though.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, i wonder if handling raw block position and passing type isnt the way forward. Maybe we need a way to identify containers in the world built with empty stacks as they havent been updated yet, and then just pass those into the OpenContainerTask. As only actually interacting with the container causes it to be saved to cache, we wouldnt have to worry about this causing spam.

Initially i was thinking that eventually i'd remove the strict typing and have PlacedContainer's generic with an enum for types and separate classes for those who require different access chains. Almost all PlacedContainer's have the same access logic so this would cover most containers in the game. Perhaps this is something that would be done on another branch, merged as a dependency before this branch is complete

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually thats a lot of work though and it might just be best to get these fixes merged. My main aversion was the callback for result generating, but as long as theres a todo im sure its fine to merge now. I can revert those specific changes i made or you can add them back, either works

… SafeContext isnt created after the overload fix is implemented and add ToDo in TargetState

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

2 participants